#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include "trace.h"

_SSL_BEGIN
_SSBASEL_BEGIN

#define kMaxTraceInfoSize 4096
#define kMaxTraceLevelSize 50

#define TL_URGENT_STRING_A              "URGENT "
#define TL_ERROR_STRING_A               "ERROR  "
#define TL_WARNING_STRING_A             "WARNING"
#define TL_INFO_STRING_A                "INFO   "
#define TL_DEBUG_STRING_A               "DEBUG  "
#define TL_DEBUG_DETAIL_HEADER_STRING_A "DEB_DETAIL(%d)"
#define TL_TRACE_STRING_A               "TRACE  "
#define TL_COMP_STRING_A                "COMPAT "

#define TL_URGENT_STRING_W              L"URGENT "
#define TL_ERROR_STRING_W               L"ERROR  "
#define TL_WARNING_STRING_W             L"WARNING"
#define TL_INFO_STRING_W                L"INFO   "
#define TL_DEBUG_STRING_W               L"DEBUG  "
#define TL_DEBUG_DETAIL_HEADER_STRING_W L"DEB_DETAIL(%d)"
#define TL_TRACE_STRING_W               L"TRACE  "
#define TL_COMP_STRING_W                L"COMPAT "


static PFNTraceCBA g_trace_cb_a = 0;
static PFNTraceCBW g_trace_cb_w = 0;
static PFNTraceFormatCBA g_trace_format_cb_a = 0;
static PFNTraceFormatCBW g_trace_format_cb_w = 0;
static unsigned int g_trace_levels = TRACE_LEVEL_ONLY(TRACE_LEVEL_MASK);
static int g_trace_detail_debug_levels_cnt = 0;
static unsigned int g_trace_detail_debug_leves[TRACE_DBG_DETAIL_LEVEL(TRACE_LEVEL_DBG_DETAIL_MASK)] = {0};

//
static int default_trace_format_cb_a(char *info, size_t info_size, unsigned int level,
	const char *fmt, va_list vl)
{
	HRESULT ret = E_FAIL;
	char trace_level_string[kMaxTraceLevelSize] = {0};
	get_trace_level_string_a(level, trace_level_string, _countof(trace_level_string));

	SYSTEMTIME sys_time = {0};
	GetLocalTime(&sys_time);

	if(StringCchPrintfA(info, info_size,
		"%s<%02d:%02d:%02d.%03d,T:%04d> : ",
		trace_level_string, sys_time.wHour, sys_time.wMinute,
		sys_time.wSecond, sys_time.wMilliseconds, GetCurrentThreadId()) == S_OK)
	{
		info_size -= strlen(info);
		info += strlen(info);
	}

	return StringCchVPrintfA(info, info_size, fmt, vl) == S_OK;
}

static int default_trace_format_cb_w(wchar_t *info, size_t info_size, unsigned int level,
	const wchar_t *fmt, va_list vl)
{
	HRESULT ret = E_FAIL;
	wchar_t trace_level_string[kMaxTraceLevelSize] = {0};
	get_trace_level_string_w(level, trace_level_string, _countof(trace_level_string));

	SYSTEMTIME sys_time = {0};
	GetLocalTime(&sys_time);

	if(StringCchPrintfW(info, info_size,
		L"%s<%02d:%02d:%02d.%03d,T:%04d> : ",
		trace_level_string, sys_time.wHour, sys_time.wMinute,
		sys_time.wSecond, sys_time.wMilliseconds, GetCurrentThreadId()) == S_OK)
	{
		info_size -=  wcslen(info);
		info += wcslen(info);
	}

	return StringCchVPrintfW(info, info_size, fmt, vl) == S_OK;
}

static void trace_cb_entry_a(unsigned int level, const char *info)
{
	if(g_trace_cb_a != NULL)
	{
		g_trace_cb_a(level, info);
	}
#ifndef TRACE_DISABLE_PRE_OUTPUT
	else
	{
		OutputDebugStringA(info);
		OutputDebugStringA("\n");
		printf_s("%s\n", info);
	}
#endif
}

static void trace_cb_entry_w(unsigned int level, const wchar_t *info)
{
	if(g_trace_cb_w != NULL)
	{
		g_trace_cb_w(level, info);
	}
#ifndef TRACE_DISABLE_PRE_OUTPUT
	else
	{
		OutputDebugStringW(info);
		OutputDebugStringW(L"\n");
		wprintf_s(L"%s\n", info);
	}
#endif
}

static bool is_detail_dbg_level_defined(unsigned int detail_debug_level)
{
	if(detail_debug_level == 0)
		return false;

	bool ret = false;
	int loop_count = 0;
	int count = _countof(g_trace_detail_debug_leves);
	for(int i=0; i<count && loop_count<g_trace_detail_debug_levels_cnt; i++)
	{
		if(g_trace_detail_debug_leves[i] == 0)
			continue;
		loop_count++;
		if(g_trace_detail_debug_leves[i] == detail_debug_level)
		{
			ret = true;
			break;
		}
	}

	return ret;
}

static bool is_level_defined(unsigned int level)
{
	if(!SS_FLAG_ISSET(g_trace_levels, TRACE_LEVEL_ONLY(level)))
		return false;
	if(TRACE_LEVEL_ONLY(level) != TL_DEBUG_DETAIL)
		return true;
	return is_detail_dbg_level_defined(TRACE_DBG_DETAIL_LEVEL(level));
}

void trace_va(unsigned int level, const char *fmt, ...)
{
	if(!is_level_defined(level))
		return;

	va_list vlist;
	va_start(vlist, fmt);

	char trace_buffer[kMaxTraceInfoSize];
	trace_receiver_get_format_cb_a()(trace_buffer, _countof(trace_buffer), level,
		fmt, vlist);

	va_end(vlist);

	trace_cb_entry_a(level, trace_buffer);
}

void trace_vw(unsigned int level, const wchar_t *fmt, ...)
{
	if(!is_level_defined(level))
		return;

	va_list vlist;
	va_start(vlist, fmt);

	wchar_t trace_buffer[kMaxTraceInfoSize];
	trace_receiver_get_format_cb_w()(trace_buffer, _countof(trace_buffer), level,
		fmt, vlist);

	va_end(vlist);

	trace_cb_entry_w(level, trace_buffer);
}

void trace_a(unsigned int level, const char *info)
{
	if(!is_level_defined(level))
		return;

	char trace_buffer[kMaxTraceInfoSize];
	trace_receiver_get_format_cb_a()(trace_buffer, _countof(trace_buffer), level,
		info, 0);

	trace_cb_entry_a(level, trace_buffer);
}

void trace_w(unsigned int level, const wchar_t *info)
{
	if(!is_level_defined(level))
		return;

	wchar_t trace_buffer[kMaxTraceInfoSize];
	trace_receiver_get_format_cb_w()(trace_buffer, _countof(trace_buffer), level,
		info, 0);

	trace_cb_entry_w(level, trace_buffer);
}

PFNTraceCBA trace_receiver_set_cb_a(PFNTraceCBA cb)
{
	PFNTraceCBA ret = g_trace_cb_a;
	g_trace_cb_a = cb;
	return ret;
}

PFNTraceCBW trace_receiver_set_cb_w(PFNTraceCBW cb)
{
	PFNTraceCBW ret = g_trace_cb_w;
	g_trace_cb_w = cb;
	return ret;
}

unsigned int trace_receiver_set_levels(unsigned int levels)
{
	unsigned int ret = g_trace_levels;
	g_trace_levels = levels;
	return ret;
}

unsigned int trace_receiver_get_levels()
{
	return g_trace_levels;
}

void trace_receiver_detail_debug_levels_clear()
{
	memset(g_trace_detail_debug_leves, 0, sizeof(g_trace_detail_debug_leves));
	g_trace_detail_debug_levels_cnt = 0;
}

bool trace_receiver_detail_debug_levels_append(unsigned int detail_debug_level)
{
	if(detail_debug_level == 0)
		return false;
	size_t count = _countof(g_trace_detail_debug_leves);
	int free_slot_index = -1;
	int exist_slot_index = -1;
	for(size_t i=0; i<count; i++)
	{
		if(g_trace_detail_debug_leves[i] == detail_debug_level)
		{
			exist_slot_index = i;
			break;
		}

		if(free_slot_index >= 0)
			continue;

		if(g_trace_detail_debug_leves[i] == 0)
		{
			free_slot_index = i;
		}
	}

	if(exist_slot_index != -1)
		return true;
	if(free_slot_index == -1)
		return false;
	g_trace_detail_debug_leves[free_slot_index] = detail_debug_level;
	g_trace_detail_debug_levels_cnt++;
	return true;
}

bool trace_receiver_detail_debug_levels_remove(unsigned int detail_debug_level)
{
	if(detail_debug_level == 0)
		return false;

	bool ret = false;

	int loop_count = 0;
	size_t count = _countof(g_trace_detail_debug_leves);
	for(size_t i=0; i<count && loop_count<g_trace_detail_debug_levels_cnt; i++)
	{
		if(g_trace_detail_debug_leves[i] == 0)
			continue;

		loop_count++;

		if(g_trace_detail_debug_leves[i] == detail_debug_level)
		{
			g_trace_detail_debug_leves[i] = 0;
			g_trace_detail_debug_levels_cnt--;
			ret = true;
			break;
		}
	}

	return ret;
}

PFNTraceFormatCBA trace_receiver_set_format_cb_a(PFNTraceFormatCBA cb)
{
	PFNTraceFormatCBA ret = g_trace_format_cb_a;
	g_trace_format_cb_a = cb;
	return ret;
}

PFNTraceFormatCBA trace_receiver_get_format_cb_a()
{
	if(g_trace_format_cb_a == 0)
		g_trace_format_cb_a = default_trace_format_cb_a;
	return g_trace_format_cb_a;
}

PFNTraceFormatCBW trace_receiver_set_format_cb_w(PFNTraceFormatCBW cb)
{
	PFNTraceFormatCBW ret = g_trace_format_cb_w;
	g_trace_format_cb_w = cb;
	return ret;
}

PFNTraceFormatCBW trace_receiver_get_format_cb_w()
{
	if(g_trace_format_cb_w == 0)
		g_trace_format_cb_w = default_trace_format_cb_w;
	return g_trace_format_cb_w;
}

bool get_trace_level_string_a(unsigned int level, char *level_string, size_t level_string_size)
{
	if(level_string == NULL || level_string_size < kMaxTraceLevelSize)
		return false;

	switch(TRACE_LEVEL_ONLY(level))
	{
	case TL_URGENT:
		strcpy_s(level_string, level_string_size, TL_URGENT_STRING_A);
		break;
	case TL_ERROR:
		strcpy_s(level_string, level_string_size, TL_ERROR_STRING_A);
		break;
	case TL_WARNING:
		strcpy_s(level_string, level_string_size, TL_WARNING_STRING_A);
		break;
	case TL_INFO:
		strcpy_s(level_string, level_string_size, TL_INFO_STRING_A);
		break;
	case TL_DEBUG:
		strcpy_s(level_string, level_string_size, TL_DEBUG_STRING_A);
		break;
	case TL_DEBUG_DETAIL:
		sprintf_s(level_string, level_string_size, TL_DEBUG_DETAIL_HEADER_STRING_A,
			TRACE_DBG_DETAIL_LEVEL(level));
		break;
	case TL_TRACE:
		strcpy_s(level_string, level_string_size, TL_TRACE_STRING_A);
		break;
	default:
		strcpy_s(level_string, level_string_size, TL_COMP_STRING_A);
		break;
	}

	return true;
}

bool get_trace_level_string_w(unsigned int level, wchar_t *level_string, size_t level_string_size)
{
	if(level_string == NULL || level_string_size < kMaxTraceLevelSize)
		return false;

	switch(TRACE_LEVEL_ONLY(level))
	{
	case TL_URGENT:
		wcscpy_s(level_string, level_string_size, TL_URGENT_STRING_W);
		break;
	case TL_ERROR:
		wcscpy_s(level_string, level_string_size, TL_ERROR_STRING_W);
		break;
	case TL_WARNING:
		wcscpy_s(level_string, level_string_size, TL_WARNING_STRING_W);
		break;
	case TL_INFO:
		wcscpy_s(level_string, level_string_size, TL_INFO_STRING_W);
		break;
	case TL_DEBUG:
		wcscpy_s(level_string, level_string_size, TL_DEBUG_STRING_W);
		break;
	case TL_DEBUG_DETAIL:
		swprintf_s(level_string, level_string_size, TL_DEBUG_DETAIL_HEADER_STRING_W,
			TRACE_DBG_DETAIL_LEVEL(level));
		break;
	case TL_TRACE:
		wcscpy_s(level_string, level_string_size, TL_TRACE_STRING_W);
		break;
	default:
		wcscpy_s(level_string, level_string_size, TL_COMP_STRING_W);
		break;
	}

	return true;
}

_SSBASEL_END
_SSL_END
